### ТЗ-1 Необходимо  объявить базовый класс Validator, в котором

должен отсутствовать инициализатор (магический метод `__init__`) и объявлен метод со следующей сигнатурой:

```python
def _is_valid(self, data): ...
```

Этот метод возвращает булево значение True, если данные (data) корректны с точки зрения валидатора, и False - в противном случае. 

Но в базовом классе `Validator` он должен генерировать исключение командой:

> raise NotImplementedError('в классе не переопределен метод _is_valid')

Затем, нужно объявить дочерний класс `FloatValidator` для валидации вещественных чисел. 

Объекты этого класса создаются командой:
```python
float_validator = FloatValidator(min_value, max_value)
```

где `min_value` - минимально допустимое значение; 

`max_value` - максимально допустимое значение.

Пользоваться объектами класса `FloatValidator` предполагается следующим образом:

```python
res = float_validator(value)
```

где `value` - проверяемое значение (должно быть вещественным и находиться в диапазоне `[min_value; max_value]`). 

Данный валидатор должен возвращать `True`, если значение `value` проходит проверку, и `False` - в противном случае.

Пример использования классов (эти строчки писать не нужно):

```python
float_validator = FloatValidator(0, 10.5)
res_1 = float_validator(1)  # False (целое число, а не вещественное)
res_2 = float_validator(1.0)  # True
res_3 = float_validator(-1.0)  # False (выход за диапазон [0; 10.5])
```


<details>
<summary>Решение </summary>

```python
class Validator:

    def _is_valid(self, data):
        raise NotImplementedError('в классе не переопределен метод _is_valid')


class FloatValidator(Validator):
    def __init__(self, min_value, max_value):
        self.min_value = min_value
        self.max_value = max_value

    def _is_valid(self, data):
        return type(data) == float and self.min_value <= data <= self.max_value


    def __call__(self, data):
        return self._is_valid(data)
```
</details>


## ТЗ-2 В программе объявлены два класса:

```python
class Student:
    def __init__(self, fio, group):
        self._fio = fio  # ФИО студента (строка)
        self._group = group # группа (строка)
        self._lect_marks = []  # оценки за лекции
        self._house_marks = []  # оценки за домашние задания

    def add_lect_marks(self, mark):
        self._lect_marks.append(mark)

    def add_house_marks(self, mark):
        self._house_marks.append(mark)

    def __str__(self):
        return f"Студент {self._fio}: оценки на лекциях: {str(self._lect_marks)}; оценки за д/з: {str(self._house_marks)}"


class Mentor:
    def __init__(self, fio, subject):
        self._fio = fio
        self._subject = subject

```

Первый класс описывает студентов, а второй - менторов. 

Вам поручается на основе базового класса Mentor разработать еще два дочерних класса:

```
Lector - для описания лекторов;
Reviewer - для описания экспертов.
```

Объекты этих классов должны создаваться командами:

```python
lector = Lector(fio, subject)
reviewer = Reviewer(fio, subject)
```

где
```
fio - ФИО (строка); 
subject - предмет (строка). 
```

Инициализации этих параметров `(fio, subject)` должна выполняться базовым классом `Mentor`.

В самих классах `Lector` и `Reviewer` необходимо объявить метод:
```python
def set_mark(self, student, mark): ...
```

для простановки оценки `(mark)` студенту `(student)`. 

Причем, в классе `Lector` оценки добавляются в список `_lect_marks` объекта класса `Student`, а в классе `Reviewer` - в список `_house_marks`. 

Используйте для этого методы `add_lect_marks()` и `add_house_marks()` класса `Student`.

Также в классах `Lector` и `Reviewer` должен быть переопределен магический метод:

`__str__()`

для формирования следующей информации об объектах:

> для объектов класса Lector: Лектор <ФИО>: предмет <предмет>

> для объектов класса Reviewer: Эксперт <ФИО>: предмет <предмет>

Пример использования классов :
```python
lector = Lector("Балакирев С.М.", "Информатика")
reviewer = Reviewer("Гейтс Б.", "Информатика")
students = [Student("Иванов А.Б.", "ЭВМд-11"), Student("Гаврилов С.А.", "ЭВМд-11")]
persons = [lector, reviewer]
lector.set_mark(students[0], 4)
lector.set_mark(students[1], 2)
reviewer.set_mark(students[0], 5)
reviewer.set_mark(students[1], 3)
for p in persons + students:
    print(p)
# в консоли будет отображено:
# Лектор Балакирев С.М.: предмет Информатика
# Эксперт Гейтс Б.: предмет Информатика
# Студент Иванов А.Б.: оценки на лекциях: [4]; оценки за д/з: [5]
# Студент Гаврилов С.А.: оценки на лекциях: [2]; оценки за д/з: [3]
```

Подумайте, где в этой программе полиморфизм.


<details>
<summary>Решение </summary>

```python
class Student:
    def __init__(self, fio, group):
        self._fio = fio
        self._group = group
        self._lect_marks = []  # оценки за лекции
        self._house_marks = []  # оценки за домашние задания

    def add_lect_marks(self, mark):
        self._lect_marks.append(mark)

    def add_house_marks(self, mark):
        self._house_marks.append(mark)

    def __str__(self):
        return f"Студент {self._fio}: оценки на лекциях: {str(self._lect_marks)}; оценки за д/з: {str(self._house_marks)}"


class Mentor:
    def __init__(self, fio, subject):
        self._fio = fio
        self._subject = subject

        
class Lector(Mentor):
    def __init__(self, fio, subject):
        super().__init__(fio, subject)
        # super(Lector, self).__init__()
    def set_mark(self, student, mark):
        student.add_lect_marks(mark)

    def __str__(self):
        return f'Лектор {self._fio}: предмет {self._subject}'
        
class Reviewer (Mentor):
    def __init__(self, fio, subject):
        super().__init__(fio, subject)
    def set_mark(self, student, mark):
        student.add_house_marks(mark)
    def __str__(self):
        return f'Эксперт {self._fio}: предмет {self._subject}'
```
</details>




### ТЗ-2 необходимо объявить базовый класс ShopInterface с абстрактным методом:

```python
def get_id(self): ...
```
В самом методе должно генерироваться исключение командой:

`raise NotImplementedError('в классе не переопределен метод get_id')`
Инициализатор в классе ShopInterface прописывать не нужно.

Далее объявите дочерний класс `ShopItem` (от базового класса ShopInterface), объекты которого создаются командой:

`item = ShopItem(name, weight, price)`

где

`name` - название товара (строка);

`weight` - вес товара (любое положительное число);

`price` - цена товара (любое положительное число).

В каждом объекте класса ShopItem должны формироваться локальные атрибуты с именами `_name, _weight, _price` и соответствующими значениями. 

Также в объектах класса `ShopItem` должен автоматически формироваться локальный приватный атрибут `__id` с уникальным (для каждого товара) целым значением.

В классе `ShopItem` необходимо переопределить `метод get_id() `базового класса так, чтобы он (метод) возвращал значение атрибута __id.


<details>
<summary>Решение </summary>

```python
class ShopInterface:
    def get_id(self):
        raise NotImplementedError('в классе не переопределен метод get_id')


class ShopItem(ShopInterface):
    x = 0

    def __init__(self, name, weight, price):
        self._name = name
        self._weight = weight
        self._price = price
        self.__id = ShopItem.x
        ShopItem.x += 1

    def get_id(self):
        return self.__id
```
</details>


